/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package collections.list;

import collections.exceptions.MyIllegalArgumentException;
import collections.list.interfaces.RandomAccess;
import collections.list.interfaces.MyList;
import collections.exceptions.MyIndexOutOfBoundsException;
import collections.exceptions.MyNoSuchElementException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Objects;

/**
 *
 * @author Iaroslav_Mazai
 */
public class MyArrayList implements MyList, RandomAccess {

	private static final double OVERFLOW_MODIFIER = 1.5;
	private static final int DEFAULT_CAPACITY = 10;
	// Setting to true should improve performance
	private Object[] data;
	private Integer capacity;
	private Integer size;

	public MyArrayList() {
		this(DEFAULT_CAPACITY);
	}

	public MyArrayList(MyList c) {
		size = c.size();
		capacity = (int) Math.ceil(size * OVERFLOW_MODIFIER);
		data = new Object[capacity];
		System.arraycopy(c.toArray(), 0, data, 0, size);
	}

	public MyArrayList(int initialCapacity) {
		if (initialCapacity < 0){
			throw new MyIllegalArgumentException();
		}
		data = new Object[initialCapacity];
		capacity = initialCapacity;
		size = 0;
	}

	@Override
	public int size() {
		return size;
	}

	@Override
	public boolean isEmpty() {
		return size() <= 0;
	}

	@Override
	public Iterator iterator() {
		return new AscIterator();
	}

	@Override
	public Object[] toArray() {
		return Arrays.copyOf(data, size);
	}

	@Override
	public void clear() {
		for (int i = 0; i < capacity; i++) {
			data[i] = null;
		}

		capacity = 0;
		size = 0;
	}

	@Override
	public Object get(int index) {
		checkIndex(index);

		return data[index];
	}

	@Override
	public Object set(int index, Object element) {
		checkIndex(index);

		Object previous = data[index];
		data[index] = element;
		return previous;
	}

	// Just an utility function
	private void checkAndUpdateCapacity(int capacityNeeded) {
		if (capacity == 0) {
			capacity++;
		}
		if (capacity <= capacityNeeded) {
			// Copy elements from old array
			int newCapacity = (int) Math.ceil(capacity * OVERFLOW_MODIFIER);
			Object[] newData = new Object[newCapacity];

			System.arraycopy(data, 0, newData, 0, size);
			data = newData;
			capacity = newCapacity;
		}
	}

	private void checkIndex(int index) {
		if (index < 0 || index >= this.size) {
			throw new MyIndexOutOfBoundsException();
		}
	}

	private void shiftDataRight(int index, int length) {
		for (int i = size - 1 + length; i >= index + length; i--) {
			data[i] = data[i - length];
		}
	}

	@Override
	public void add(int index, Object element) {
		if (index != 0) {
			checkIndex(index - 1);
		}

		checkAndUpdateCapacity(size + 1);

		shiftDataRight(index, 1);

		size++;

		data[index] = element;
	}

	@Override
	public Object remove(int index) {
		checkIndex(index);

		// To return later
		Object oldObject = data[index];

		// Shift all data left by 1
		for (int i = index; i < size - 1; i++) {
			data[i] = data[i + 1];
		}

		// Remove last element (there is duplicate) adn update size
		data[size - 1] = null;
		size--;

		return oldObject;
	}

	@Override
	public int indexOf(Object o) {
		for (int i = 0; i < size; i++) {
			if (get(i) == null) {
				if (o == null) {
					return i;
				}
			} else if (get(i).equals(o)) {
				return i;
			}
		}
		return -1;
	}

	@Override
	public void add(Object e) {
		checkAndUpdateCapacity(size + 1);

		data[size] = e;
		size++;
	}

	@Override
	public void addAll(Object[] c) {
		addAll(size, c);
	}

	@Override
	public void addAll(int index, Object[] c) {
		if (c == null) {
			return;
		}

		if (index != 0) {
			checkIndex(index - 1);
		}

		checkAndUpdateCapacity(size + c.length);

		shiftDataRight(index, c.length);

		size += c.length;

		System.arraycopy(c, 0, data, index, c.length);
	}

	@Override
	public String toString() {
		if (isEmpty()) {
			return "[]";
		}
		String result = "";

		result += "[";
		for (int i = 0; i < size - 1; i++) {
			result += data[i] + ", ";
		}
		result += data[size - 1];
		result += "]";

		return result;
	}

	private class AscIterator implements Iterator {

		int index = -1;

		public AscIterator() {
			index = -1;
		}

		@Override
		public boolean hasNext() {
			return (index < size - 1);
		}

		@Override
		public Object next() {
			if (!hasNext()) {
				throw new MyNoSuchElementException();
			}
			index++;
			return data[index];
		}

		@Override
		public void remove() {
			MyArrayList.this.remove(index);
		}
	}

	@Override
	public int hashCode() {
		int hash = 5;
		hash = 67 * hash + Arrays.deepHashCode(this.data);
		hash = 67 * hash + Objects.hashCode(this.capacity);
		hash = 67 * hash + Objects.hashCode(this.size);
		return hash;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		final MyArrayList other = (MyArrayList) obj;
		if (!Objects.equals(this.size, other.size)) {
			return false;
		}
		if (!Arrays.deepEquals(this.data, other.data)) {
			return false;
		}
		return true;
	}
}
